package com.jeff.cryptor.lib;

import android.text.TextUtils;
import android.util.Log;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Author: duff
 * Date: 2018/11/19
 * Version: 1.0.0
 * Description: AES加密
 */
public class AES {
    private static final String TAG = "aes";

    private static final String IVPARA = "0102030405060708";
    private static final byte[] DEFAULT_KEY = {
            84, 65, -33, 92
            , 57, 90, -104, 0
            , 112, 74, 69, -53
            , 106, -122, -107, -95
    };
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";


    /************************* 加密 *************************/

    /**
     * @param data The data to encrypted.
     * @return Encrypted data.
     */
    public static String encrypt(String data) {
        return encrypt(data, DEFAULT_KEY);
    }

    public static String encrypt(String data, byte[] key) {
        return encrypt(data, key, IVPARA.getBytes());
    }

    public static String encrypt(String data, byte[] key, byte[] iv) {
        return encrypt(data, key, iv, ALGORITHM);
    }

    public static String encrypt(String data, byte[] key, byte[] iv, String transformation) {
        byte[] encrypted = encrypt(data.getBytes(), key, iv, transformation);
        if (encrypted == null)
            return null;
        return toHex(encrypted);
    }

    /**
     * @param data           The data to encrypt.
     * @param key            The key.
     * @param iv             The buffer with the IV. The contents of the
     *                       buffer are copied to protect against subsequent modification.
     * @param transformation The name of the transformation, e.g., <i>AES/CBC/PKCS5Padding</i>.
     * @return The bytes of AES encryption or null.
     */
    private static byte[] encrypt(byte[] data, byte[] key, byte[] iv, String transformation) {
        if (iv == null || key == null || data == null || TextUtils.isEmpty(transformation)) {
            Log.d(TAG, "Encrypt params is invalid.");
            return null;
        }
        byte[] encrypted = null;
        try {
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
            encrypted = cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encrypted;
    }


    /************************* 解密 *************************/

    /**
     * @param data The encrypted data to decrypt.
     * @return Decrypted data.
     */
    public static String decrypt(String data) {
        return decrypt(data, DEFAULT_KEY);
    }

    public static String decrypt(String data, byte[] key) {
        return decrypt(data, key, IVPARA.getBytes());
    }

    public static String decrypt(String data, byte[] key, byte[] iv) {
        return decrypt(data, key, iv, ALGORITHM);
    }

    public static String decrypt(String data, byte[] key, byte[] iv, String transformation) {
        byte[] encrypted = toByte(data);
        if (encrypted == null)
            return null;
        byte[] result = decrypt(encrypted, key, iv, transformation);
        if (result == null)
            return null;

        return new String(result);
    }

    /**
     * @param encrypted      The encrypted data to decrypt.
     * @param key            The key.
     * @param iv             The buffer with the IV. The contents of the
     *                       buffer are copied to protect against subsequent modification.
     * @param transformation The name of the transformation, e.g., <i>AES/CBC/PKCS5Padding</i>.
     * @return The decrypted data.
     */
    private static byte[] decrypt(byte[] encrypted, byte[] key, byte[] iv, String transformation) {
        if (iv == null || key == null || encrypted == null || TextUtils.isEmpty(transformation)) {
            Log.d(TAG, "Decrypt params is invalid.");
            return null;
        }
        byte[] decrypted = null;
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
            decrypted = cipher.doFinal(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decrypted;
    }

    public static final int ROUND_8BITS = 0x100;
    public static final int ROUND_4BITS = 0x10;
    public static final int RADIX_16 = 16;

    private static byte[] toByte(String hexString) {
        try {
            int len = hexString.length() / 2;
            byte[] result = new byte[len];
            for (int i = 0; i < len; i++) {
                result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), RADIX_16).byteValue();
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String toHex(byte[] buf) {
        if (buf == null) {
            return "";
        }

        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private final static String HEX = "0123456789ABCDEF";

    private static void appendHex(StringBuffer sb, byte b) {
        final int flagOfLowByte = 0x0f;
        final int shift = 4;
        sb.append(HEX.charAt((b >> shift) & flagOfLowByte)).append(HEX.charAt(b & flagOfLowByte));
    }

}